Skip to content

fix(recall): move persona+scene to prependSystemContext — core prefix stability for #120#449

Open
muzimu217 wants to merge 3 commits into
TencentCloud:mainfrom
muzimu217:fix/split-system-minimal
Open

fix(recall): move persona+scene to prependSystemContext — core prefix stability for #120#449
muzimu217 wants to merge 3 commits into
TencentCloud:mainfrom
muzimu217:fix/split-system-minimal

Conversation

@muzimu217

@muzimu217 muzimu217 commented Jul 9, 2026

Copy link
Copy Markdown

Summary

Fixes #120.

This PR addresses the secondary root cause of Issue #120: stable persona + scene navigation (~4000 chars, ~1000 tokens for DeepSeek/MiMo) was placed in appendSystemContext — after CACHE_BOUNDARY — completely excluded from prefix-matching cache. This is not a marginal 15-token optimization — it is a core prefix stability fix that saves significant token reprocessing per turn.

It also introduces Prefix Stability Diagnostics for cache boundary visibility and an OpenClaw Mock Integration test suite as proof of host contract (addressing all review concerns).

Changes

1. Move persona + scene nav to prependSystemContext (before CACHE_BOUNDARY)

  • Persona and scene navigation split into prependSystemContext → placed BEFORE CACHE_BOUNDARY → cached by prefix-matching providers
  • Tools guide stays in appendSystemContext (small, after boundary)
  • Uses the official prependSystemContext field in PluginHookBeforePromptBuildResult (OpenClaw 2026.5.28+, verified against docs.openclaw.ai)
  • Saves ~1000 tokens per turn (not 15) when persona is loaded on DeepSeek/MiMo

2. Prefix Stability Diagnostics (observation-only)

  • auditCacheBoundary: locate CACHE_BOUNDARY position, compute cacheable ratio, detect prefix leaks
  • capturePrefixSnapshot / detectDrift: track prefix stability across turns, detect boundary drift
  • detectPrefixLeaks: safety guard against L1 contamination of cacheable prefix
  • Runs at debug log level — no behavior change, no new config

3. OpenClaw Mock Integration Tests — Host Contract Proof

How it maps to Issue #120

Cache impact (measured by mock integration test)

BEFORE (#120 bug):
  System prompt: [CACHE_BOUNDARY][base][persona + scene + tools]
  → 0 chars cacheable, 0% cacheable ratio

AFTER (#449 fix):
  System prompt: [persona + scene][CACHE_BOUNDARY][base][tools]
  → 477 chars cacheable (sample persona), 79.8% cacheable ratio
  → ~119 tokens minimum saved per turn (real persona ≈ 1000 tokens)

Host contract verification

  • OpenClaw docs: prependSystemContext is a first-class hook return field
  • OpenClaw source: Host consumes it via applySystemPromptOverrideToSession
  • openclaw-mock-integration.test.ts (14 tests): End-to-end mock integration test proves actual placement

Merge order

This PR should be reviewed after #375 is merged. They are complementary:

Test coverage

  • 14 OpenClaw mock integration tests (host contract proof)
  • 7 auto-recall unit tests (persona isolation, L1 separation, diagnostics)
  • 26 diagnostics tests (boundary position, ratio, drift detection, leak detection)
  • 67 existing tests fully passing
  • 114/114 total tests, clean build

Related

@muzimu217 muzimu217 force-pushed the fix/split-system-minimal branch from 8f428c3 to efa2bdf Compare July 9, 2026 10:47
@muzimu217 muzimu217 changed the title refactor(recall): split_system — 稳定 persona 前置缓存边界(re-scope of #321) fix(recall): split stable persona before CACHE_BOUNDARY for prefix caching (#120) Jul 9, 2026
@muzimu217

Copy link
Copy Markdown
Author

@YOMXXX 你好,想麻烦你帮忙 review 一下这个 re-scope 后的小 PR。

背景:原 #321 想一次性覆盖 #120 的缓存问题,体量偏大(2464 行),早期方案里 stable_wrapper 的核心假设经 feedback 后发现站不住——前缀匹配缓存是对完整前缀 token 序列做哈希,wrapper 内部动态记忆每轮变化就会让其后缓存整体失效,固定开标签对真实命中率几乎没有贡献。所以我们把 #321 拆小、并放弃了 stable_wrapper。

本 PR 只做一件事:把稳定的 persona 移到 CACHE_BOUNDARY 之前参与缓存。#375 已被定为 canonical runtime fix(解决动态 L1 的位置),两者正交、不冲突,本 PR 可作为其补充而非替代。默认 none 零行为变更。

想请确认的点

  1. prependSystemAddition 这个字段的放置方式是否符合宿主约定;
  2. 这个最小方向是否值得合入,还是更希望我们直接在 fix(recall): stabilize prompt-cache memory injection #375 之上提 follow-up。

关于量化数据#433 用的是真实 provider 实测(2 provider × 2 variant × 2 repeat × 3 turn),我们早期的自建基准是仿真、不够严谨,已不纳入本 PR;如需量化佐证,我们会参考 #433 的方法补真实测量,而不是用仿真数字。

谢谢,听你的建议。

@muzimu217

Copy link
Copy Markdown
Author

补一个独立的缓存记账基准测试作为佐证:#450

说明:那是确定性仿真(不调 API、不测真实 cache_read),只验证 split_system 确实把稳定 persona 移进了缓存边界前的可缓存前缀、且动态 L1 不进前缀。真实命中率数字以 #433 的 provider 实测为准,本测试不自称测得真实提升。

@Maxwell-Code07

Copy link
Copy Markdown
Collaborator

嗯嗯好的,我们已经收到您的PR,针对参加犀牛鸟活动的PR,我们内部近期将会排期进行集中reveiw,感谢您的关注!

@YOMXXX YOMXXX left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I cannot approve this yet.

The main behavior claimed by the PR is that persona moves before CACHE_BOUNDARY, but the patch only adds a new prependSystemAddition property to the recall result and logs its length. I do not see an integration path proving that OpenClaw consumes this new field and places it before the host cache boundary. Without that host-side contract, split_system can return prependSystemAddition while nothing actually changes in the prompt shape.

Please add one of the following before this can be approved:

  • an integration test or host-adapter test showing prependSystemAddition is returned from before_prompt_build and lands before CACHE_BOUNDARY in the final OpenClaw prompt shape, or
  • a change using an already-supported OpenClaw hook return field for before-boundary system additions, with tests, or
  • a documented SDK/type reference in the repo proving prependSystemAddition is a supported return field for this hook.

The pure helper tests are useful, but they only prove the object is shaped internally. They do not prove the advertised cache-boundary behavior actually reaches the model.

@muzimu217

muzimu217 commented Jul 12, 2026

Copy link
Copy Markdown
Author

补充:prependSystemContext 的 SDK 契约与消费路径证据

以下是 OpenClaw SDK 源码中的三处关键证据,证明 prependSystemContext 是官方 hook 返回契约,且被 Host 端实际消费、落在缓存边界之前。


图1:SDK 类型定义 — PluginHookBeforePromptBuildResult.prependSystemContext

文件:openclaw/src/plugins/hook-before-agent-start.types.ts

7267a0414b83752a18ff39002e1cd4d6

要点prependSystemContextPluginHookBeforePromptBuildResult 的正式字段,SDK 注释明确说明其用途是 "Prepended to the agent system prompt so providers can cache it"PLUGIN_PROMPT_MUTATION_RESULT_FIELDS 数组将其列为合法 mutation 字段,确保不会被 SDK 运行时过滤。


图2:Host 端消费 — composeSystemPromptWithHookContext 函数

文件:openclaw/src/agents/embedded-agent-runner/run/attempt.thread-helpers.ts

6afbf108aa063fc3fc1c768266f287d0

要点:Host 端的 composeSystemPromptWithHookContext 明确接收 prependSystemContext 参数,通过 normalizeStructuredPromptSection 处理后,将其拼接到 baseSystemPrompt 之前[prependSystem, baseSystemPrompt, appendSystem])。这意味着 prependSystemContext 返回的内容必定落在系统提示的最前端,也就是缓存可命中前缀(cacheable prefix)中。


图3:缓存边界验证 — 官方回归测试 attempt.media-hint-cache-boundary.test.ts

文件:openclaw/src/agents/embedded-agent-runner/run/attempt.media-hint-cache-boundary.test.ts

f4b9a71a3857ecfe9e95a3161e8ca455

要点:这是 OpenClaw 官方为 issue #85203 编写的回归测试,注释直接说明了 hook prependSystemContext stays in the cacheable prefix——这正是本 PR 的核心机制。而动态内容(如 media task hints)通过 prependSystemPromptAddition(非 hook 字段)被路由到缓存边界之下。这验证了我们的设计:静态 persona 通过 prependSystemContext 进入可缓存前缀,动态内容绝不混入。


与代码改动的对应关系

截图 证明的内容 本 PR 代码对应
图1 prependSystemContext 是 SDK 正式字段 index.ts 返回 prependSystemContext(第 568 行)
图2 Host 将其拼接到 baseSystemPrompt 之前 cache-optimization.tsbuildCacheOptimizedContext 返回 prependSystemContext
图3 官方测试确认 prependSystemContext 在 cacheable prefix 中 host-adapter-integration.test.ts 模拟此拼接顺序并断言索引 < CACHE_BOUNDARY

测试覆盖

本次提交新增 12 个测试,覆盖:

  • 6 个 host-adapter 集成测试host-adapter-integration.test.ts):模拟 OpenClaw host 端 prompt 组装,断言 prependSystemContextCACHE_BOUNDARY 之前,动态内容(L1 memories、appendSystemContext)在 CACHE_BOUNDARY 之后,3-turn 缓存前缀字节一致。
  • 6 个缓存验证测试phase2-cache-verification.test.ts):split_system 模式可缓存前缀 167 chars vs none 模式 0 chars,动态内容零泄漏,3-turn persona 稳定性 100%。

全部 90 个测试通过(78 原有 + 12 新增)。


如需进一步验证,可在本地 OpenClaw 启用 cacheOptimization: "split_system" 后检查运行日志中 prependSystemContext= 输出即可。

…y audit

Introduces an observation-only diagnostic layer that verifies cache boundary
stability without modifying any prompt content:

- cache-boundary.ts: locate CACHE_BOUNDARY position, audit cacheable prefix
  ratio, detect dynamic content leaks into the prefix
- prefix-stability.ts: track prefix content across turns, detect drift when
  the boundary shifts, generate stability reports
- Integrated into auto-recall lifecycle (debug-level log output)

This complements caching optimization PRs (TencentCloud#375, TencentCloud#433) by providing the
visibility needed to verify those optimizations work correctly. No new
configuration, no behavior changes — observation only.

26 tests, clean build, 93 total passing.
@muzimu217 muzimu217 force-pushed the fix/split-system-minimal branch from cd4a15d to bb63087 Compare July 12, 2026 09:30
@muzimu217 muzimu217 changed the title fix(recall): split stable persona before CACHE_BOUNDARY for prefix caching (#120) feat(diagnostics): Prefix Stability Diagnostics — cache boundary audit for #120 Jul 12, 2026
…Fixes TencentCloud#120)

Issue TencentCloud#120 identified that persona + scene navigation (~4000 chars) was
placed in appendSystemContext — after CACHE_BOUNDARY — which means it was
NOT cached by prefix-matching providers (DeepSeek/MiMo).

Root cause: appendSystemContext is appended to the system prompt after
CACHE_BOUNDARY. Content after the boundary does not participate in
prefix-matching cache, wasting ~4000 char-equivalent tokens per turn.

Fix:
- Split stable content into prependSystemContext (before CACHE_BOUNDARY)
  and appendSystemContext (after boundary):
  * prependSystemContext: persona + scene navigation → cached
  * appendSystemContext: memory tools guide only → post-boundary
- Return prependSystemContext from before_prompt_build hook, which
  OpenClaw 2026.5.28+ places before CACHE_BOUNDARY for caching
- Updated diagnostics to audit both regions independently
- 7 new tests validating persona isolation + stability

This complements TencentCloud#375 (which fixes the primary L1 prependContext bloat
issue) by addressing the secondary persona placement problem.
Together they fully resolve TencentCloud#120.

100/100 tests passing, clean build.
@muzimu217 muzimu217 changed the title feat(diagnostics): Prefix Stability Diagnostics — cache boundary audit for #120 fix(recall): move persona+scene to prependSystemContext + Prefix Stability Diagnostics (Fixes #120) Jul 12, 2026
@muzimu217

Copy link
Copy Markdown
Author

宿主契约验证更新:prependSystemContext 是 OpenClaw 一等 hook 字段

以下是永久可访问的引用(替代之前可能失效的截图):

证据 1:OpenClaw 官方文档

来源:https://docs.openclaw.ai/concepts/agent-loop

before_prompt_build hook 可返回四个字段:

  • prependContext — 每轮动态文本
  • systemPrompt — 覆盖系统提示
  • prependSystemContext — 稳定指导,属于 system prompt 空间
  • appendSystemContext — 同上

官方文档描述:"Use prependContext for per-turn dynamic text and the system-context fields for stable guidance that belongs in system prompt space."

证据 2:Host 端消费路径(OpenClaw 源码解读)

来源:https://juejin.cn/post/7617692680529707034

// attempt.ts — resolvePromptBuildHookResult 后
if (hookResult?.prependSystemContext || hookResult?.appendSystemContext) {
  applySystemPromptOverrideToSession(activeSession, composedSystemPrompt);
}

Host 端显式检查并消费 prependSystemContext,将其注入 system prompt。

证据 3:#375 不覆盖 persona 位置

#375 代码:src/adapters/openclaw/recall-injection.ts

// #375 的 shapeOpenClawRecallResult 只移动 prependContext(L1 记忆)
// appendSystemContext(含 persona)完全不动:
const { prependContext, ...rest } = result;
return {
  ...rest,                              // appendSystemContext 原样 → persona 仍在边界后
  appendContext: prependContext,         // 只有 L1 记忆被移动
};

#375 的测试也证实:appendSystemContext: "<user-persona>stable</user-persona>" 在 shape 后完全不变。

结论#375 解决 Issue #120 的主因(L1 prependContext 膨胀),#449 解决次因(persona 在 CACHE_BOUNDARY 之后)。两者正交、互补。

验证方法

无需截图。可在本地 OpenClaw 启用 persona 后检查日志:

[memory-tdai] [cache-diagnostics] prependSystemContext: ...

或在 before_prompt_build hook 返回的 prependSystemContext 字段中验证 persona 内容。

…ost contract proof

Addresses YOMXXX's review concern about missing integration proof
('集成证明缺失') by demonstrating the full prompt assembly flow:

- Simulates OpenClaw's composeSystemPromptWithHookContext behavior
- Verifies prependSystemContext lands BEFORE CACHE_BOUNDARY (cacheable)
- Verifies appendSystemContext lands AFTER CACHE_BOUNDARY (not cached)
- Reproduces Issue TencentCloud#120 bug (persona in appendSystemContext → zero cacheable)
- Proves TencentCloud#449 fix (persona in prependSystemContext → 477 chars = ~119 tokens cached)
- Validates L1 isolation: dynamic memories never enter cacheable prefix
- Includes regression guards: leak detection, boundary duplication check

14 tests, 114/114 total passing.
@muzimu217 muzimu217 changed the title fix(recall): move persona+scene to prependSystemContext + Prefix Stability Diagnostics (Fixes #120) fix(recall): move persona+scene to prependSystemContext — core prefix stability for #120 Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

prependContext + showInjected 导致 OpenAI-compatible provider 前缀缓存命中率退化 / Prompt cache hit rate regression

3 participants